#include "Interpreter.hpp"
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

ShellCommand::ShellCommand()
{
	this->runInBackground = false;
}

ShellCommand::~ShellCommand()
{
}

bool ShellCommand::getRunInBackground()
{
	return runInBackground;
}

void ShellCommand::setRunInBackground(bool runInBackground)
{
	this->runInBackground = runInBackground;
}

//////////////////////////////////////////////////////////////////////////////

SimpleShellCommand::~SimpleShellCommand()
{
	for(unsigned ii=0; ii<redirections.size(); ii++)
		delete redirections[ii];
}

int SimpleShellCommand::execute(ExecutionEnvironment *execEnvironment, Job *context)
{
	begin(execEnvironment, context);
	return waitForFinish(context);
}

void SimpleShellCommand::begin(ExecutionEnvironment *execEnvironment, Job *context)
{
	FdCloser fdCloser;
	bool setupFailed = false;
	ShellState *state = context->getState();
	
	// Perform expansions
	vector<string> params;
	expandAndSplit(tokens, params, context, true);
	
	if(!params.size())
		return;
	
	// Handle environment variable assignments
	// Variables are assigned temporarily (for this command only), unless there
	// is no command (all tokens are assignments), in which case the assignment
	// is added to the shell state.
	vector< pair<string,string> > assignments;
	int ii;
	for(ii=0; ii<params.size(); ii++)
	{
		if(isAssignment(params[ii]))
			assignments.push_back( pair<string,string>(namePart(params[ii]), valuePart(params[ii])) );
		else
			break;
	}
	// Remove the assignments from the parameter list
	if(ii>0)
		params.erase(params.begin(), params.begin()+ii);
	
	if(params.size())
	{
		// TODO: Make assignments that went with a command apply only to that
		// command.
		for(unsigned ii=0; ii<assignments.size(); ii++)
			state->env.set(assignments[ii].first, assignments[ii].second);
	}
	else
	{
		// If the command consisted only of assignments, export them and return
		// success.
		for(unsigned ii=0; ii<assignments.size(); ii++)
			state->env.set(assignments[ii].first, assignments[ii].second);
		childPid = -1;
		builtinRet = 0;
	}
	
	if(!setupRedirects(&fdCloser, execEnvironment, context))
		setupFailed = true;
	
	if(setupFailed)
	{
		childPid = -1;
		builtinRet = 0;
	}
	else if(params.size())
	{
		bool skipFork = false;
		
		if(isBuiltin(params[0]))
		{
			BuiltinFunc func = getBuiltin(params[0]);
			assert(func);

			try {
				childPid = -1;
				builtinRet = func(execEnvironment, context, params);
				skipFork = true;
			} catch(DisabledBuiltin e) {
				// If a builtin is disabled, fall back on trying a command with the
				// same name. (In particular, 'ls' as a builtin is disabled in
				// subshells.)
			}
		}
		
		if(!skipFork)
		{
			processManager->takeLock();
				Process *process = runProgram(params, context, execEnvironment);
				if(process) {
					childPid = process->getPid();
					processManager->registerNewProcess(process);
					context->addProcess(childPid);
				} else {
					childPid = -1;
				}
			processManager->releaseLock();
		}
	}
}

int SimpleShellCommand::waitForFinish(Job *context)
{
	if(childPid >= 0)
		return context->waitForProcess(childPid);
	else
		return builtinRet;
}

bool SimpleShellCommand::setupRedirects(FdCloser *fdCloser, ExecutionEnvironment *execEnvironment, Job *context)
{
	bool setupFailed = false;
	
	// Handle redirections
	for(int ii=0; ii<redirections.size(); ii++)
	{
		Redirection *redirection = redirections[ii];
		
		// Expand the expansion filename
		vector<string> filenameList;
		expandAndSplit(redirection->tokens, filenameList, context, false);
		
		if(filenameList.size() != 1) {
			context->reportError("Ambiguous redirect");
			setupFailed = true;
			break;
		}
		
		const char *filename = filenameList[0].c_str();
		int file;
		
		switch(redirection->type)
		{
			case REDIR_SENDTO:
				file = open(filename, O_WRONLY|O_CREAT, 0644);
				break;
			case REDIR_APPENDTO:
				file = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0644);
				break;
			case REDIR_READFROM:
				file = open(filename, O_RDONLY);
				break;
		}
		
		if(file < 0)
		{
			char message[512];
			sprintf(message, "Couldn't open file %s: %s.\n", filename, strerror(errno));
			context->reportError(message);
			setupFailed = true;
		}
		
		switch(redirection->type)
		{
			case REDIR_SENDTO:
				execEnvironment->stdoutFd = file;
				break;
			case REDIR_APPENDTO:
				execEnvironment->stdoutFd = file;
				break;
			case REDIR_READFROM:
				execEnvironment->stdinFd = file;
				break;
		}
		
		fdCloser->addFd(file);
	}
	
	return !setupFailed;
}

void SimpleShellCommand::addToken(Token tok)
{
	tokens.push_back(tok);
}

void SimpleShellCommand::addRedirection(Redirection *redir)
{
	redirections.push_back(redir);
}

string SimpleShellCommand::toString()
{
	string ret = "";
	bool first = true;
	
	for(list<Token>::iterator ii=tokens.begin(); ii!=tokens.end(); ii++)
	{
		if(first) first=false;
		else ret += "/";
		
		ret += ii->toString();
	}
	
	for(unsigned ii=0; ii<redirections.size(); ii++)
		ret += "(" + redirections[ii]->toString() + ")";
	
	return ret;
}

Process *SimpleShellCommand::runProgram(const vector<string> &params, Job *context, const ExecutionEnvironment *execEnv)
{
	int argc = params.size();
	char **argv = new char*[argc+1];
	for(int ii=0; ii<argc; ii++)
		argv[ii] = strdup(params[ii].c_str());
	argv[argc] = NULL;
	
	char **env = (char**)context->getState()->env.c_strs();
	
	string prog;
	if(needsPathLookup(argv[0]))
		prog = context->getState()->pathLookup(argv[0]);
	else
		prog = argv[0];
	
	if(access(prog.c_str(), X_OK))
	{
		string errorMsg = prog+": "+strerror(errno);
		
		if(errno == ENOENT)
			builtinRet = EX_NOTFOUND;
		else if(errno == EACCES)
			builtinRet = EX_NOEXEC;
		else
			builtinRet = errno;
		
		context->reportError(errorMsg.c_str());
		return NULL;
	}
	
	int forkResult = forkProcess(execEnv, prog.c_str(), argc, argv, env);
	if(!forkResult) return NULL;
	
	Process *ret = new Process(context, forkResult);
	
	for(int ii=0; ii<argc; ii++)
		delete argv[ii];
	delete argv;
	
	return ret;
}

int SimpleShellCommand::forkProcess(const ExecutionEnvironment *context, const char *program, int argc, char **argv, char **envVars)
{
	if(context->pty)
		context->pty->reEnable();
	
	int forkResult = fork();
	
	if(forkResult > 0)
	{
		// Parent process
		return forkResult;
	}
	else if(forkResult == 0)
	{
		trace.close();
		
		// Child process
		if(context->pty)
			context->pty->setAsLogin();
		
		if(context->stdinFd >=0)
			dup2(context->stdinFd, 0);
		if(context->stdoutFd>=0)
			dup2(context->stdoutFd, 1);
		if(context->stderrFd>=0)
			dup2(context->stderrFd, 2);
		
		closeAllExceptStd();
		
		execve(program, argv, envVars);
		trace << "execve failed in runProgram (" << program << "): "<<strerror(errno)<<"!\n";
		exit(1);
		return 0;
	}
	else
	{
		// Error
		// TODO
		trace << "fork failed in runProgram!\n";
		return 0;
	}
}

bool SimpleShellCommand::needsPathLookup(const char *str)
{
	for(int ii=0; str[ii]; ii++) {
		if(str[ii]=='/') return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////////

void PipeCommand::addCommand(SimpleShellCommand *command)
{
	if(!command) return;
	commands.push_back(command);
}

int PipeCommand::execute(ExecutionEnvironment *execEnvironment, Job *context)
{
	struct PipeFds { int in, out; };
	PipeFds *pipes;
	
	pipes = new PipeFds[commands.size()-1];
	for(int ii=0; ii<commands.size()-1; ii++)
	{
		int fds[2];
		if(pipe(fds)<0) {
			// Close any pipes opened so far
			for(int jj=0; jj<ii; jj++) {
				close(pipes[jj].in);
				close(pipes[jj].out);
			}
			// Generate an error message
			context->reportError("Failed to open pipe");
			// Return failure
			return EX_REDIRFAIL;
		}
		pipes[ii].in = fds[0];
		pipes[ii].out = fds[1];
	}
	
	for(size_t ii=0; ii<commands.size(); ii++)
	{
		ExecutionEnvironment commandEnv(*execEnvironment);
		if(ii>0)
			commandEnv.stdinFd = pipes[ii-1].in;
		if(ii+1 < commands.size())
			commandEnv.stdoutFd = pipes[ii].out;
		
		commands[ii]->begin(&commandEnv, context);
	}
	
	for(int ii=0; ii<commands.size()-1; ii++) {
		close(pipes[ii].in);
		close(pipes[ii].out);
	}
	
	int ret = 0;
	for(size_t ii=0; ii<commands.size(); ii++)
	{
		ret = commands[ii]->waitForFinish(context);
	}
	
	return ret;
}

string PipeCommand::toString()
{
	string ret="";
	for(size_t ii=0; ii<commands.size(); ii++)
	{
		if(ii) ret += " |";
		ret += commands[ii]->toString();
	}
	return ret;
}

//////////////////////////////////////////////////////////////////////////////

SequenceCommand::SequenceCommand()
{
}

SequenceCommand::~SequenceCommand()
{
	for(unsigned ii=0; ii<commands.size(); ii++)
		delete commands[ii];
}

int SequenceCommand::execute(ExecutionEnvironment *execEnvironment, Job *context)
{
	int ret = 0;
	
	for(unsigned ii=0; ii<commands.size(); ii++)
	{
		bool run = true;
		if(ops[ii] == SEQUENCE_AND) {
			if(ret != 0)
				run = false;
		} else if(ops[ii] == SEQUENCE_OR) {
			if(ret == 0)
				run = false;
		} else { //ops[ii] == SEMICOLON
		}
		
		if(run) {
			ret = commands[ii]->execute(execEnvironment, context);
		} else {
			ret = 0;
		}
	}
	
	return ret;
}

string SequenceCommand::toString()
{
	string ret = "";
	bool first=true;
	for(unsigned ii=0; ii<commands.size(); ii++)
	{
		if(first) first=false;
		else ret += "; ";
		ret += commands[ii]->toString();
	}
	return ret;
}

void SequenceCommand::addCommand(ShellCommand *command, int op)
{
	if(!command) return;
	ops.push_back(op);
	commands.push_back(command);
}

//////////////////////////////////////////////////////////////////////////////

IfCommand::IfCommand(ShellCommand *condition, ShellCommand *trueBranch, ShellCommand *falseBranch)
{
	this->condition = condition;
	this->trueBranch = trueBranch;
	this->falseBranch = falseBranch;
}

IfCommand::~IfCommand()
{
	if(condition) delete condition;
	if(trueBranch) delete trueBranch;
	if(falseBranch) delete falseBranch;
}

int IfCommand::execute(ExecutionEnvironment *execEnvironment, Job *context)
{
	bool result = condition->execute(execEnvironment, context);
	
	if(result==0) {
		if(trueBranch)
			return trueBranch->execute(execEnvironment, context);
	} else {
		if(falseBranch)
			return falseBranch->execute(execEnvironment, context);
	}
}

string IfCommand::toString()
{
	string ret = "if " + condition->toString() + "\nthen ";
	if(falseBranch) ret += trueBranch->toString()+"\nelse\n"+falseBranch->toString()+"\nfi";
	else ret += trueBranch->toString()+"\nfi";
	return ret;
}

//////////////////////////////////////////////////////////////////////////////

Redirection::Redirection()
{
	this->type = 0;
}

void Redirection::setType(int type)
{
	this->type = type;
}

void Redirection::addToken(Token tok)
{
	tokens.push_back(tok);
}

string Redirection::toString()
{
	string ret = "";
	bool first = true;
	
	switch(type)
	{
		case REDIR_SENDTO: ret += ">"; break;
		default: ret += "??"; break;
	}
	
	for(list<Token>::iterator ii=tokens.begin(); ii!=tokens.end(); ii++)
	{
		if(first) first=false;
		else ret += "/";
		
		ret += ii->toString();
	}
	
	return ret;
}

//////////////////////////////////////////////////////////////////////////////

FdCloser::~FdCloser()
{
	for(size_t ii=0; ii<fds.size(); ii++)
		close(fds[ii]);
}

void FdCloser::addFd(int fd)
{
	fds.push_back(fd);
}

